#include "BoundingBox.h"
#include "Ray.h"

BoundingBox::BoundingBox(Vector3 min, Vector3 max) {
	name = "BoundingBox";
	boundingVolume = true;

	bounds[0] = min;
	bounds[1] = max;
}

BoundingBox::~BoundingBox() {
	for (int i = 0; i < objects.size(); i++) {
		delete objects[i];
	}
}

//http://www.cs.utah.edu/~awilliam/box/box.pdf
//Determines the intersection with the given ray
Object* BoundingBox::tIntersect(const Ray& ray, float& distance) {
	Vector3 dir = ray.direction;
	dir.normalize();
	Vector3 origin = ray.origin;
	//r.normalize();

	int sign[3];
	Vector3 inv_direction = Vector3(1/dir.x, 1/dir.y, 1/dir.z);
	
	sign[0] = (inv_direction.x < 0);
	sign[1] = (inv_direction.y < 0);
	sign[2] = (inv_direction.z < 0);

	float tmin, tmax, tymin, tymax, tzmin, tzmax;

	tmin = (bounds[sign[0]].x - origin.x) * inv_direction.x;
	tmax = (bounds[1-sign[0]].x - origin.x) * inv_direction.x;
	tymin = (bounds[sign[1]].y - origin.y) * inv_direction.y;
	tymax = (bounds[1-sign[1]].y - origin.y) * inv_direction.y;
	if ((tmin > tymax) || (tymin > tmax)) {
		distance = -1;
		return this;
	}
	if (tymin > tmin) {
		tmin = tymin;
	}
	if (tymax < tmax) {
		tmax = tymax;
	}
	tzmin = (bounds[sign[2]].z - origin.z) * inv_direction.z;
	tzmax = (bounds[1-sign[2]].z - origin.z) * inv_direction.z;
	if ((tmin > tzmax) || (tzmin > tmax)) {
		distance = -1;
		return this;
	}
	//don't need this part of the algorithm, since we aren't testing an interval of the ray
	/*if (tzmin > tmin) {
		tmin = tzmin;
	}
	if (tzmax < tmax) {
		tmax = tzmax;
	}*/

	//if we have gotten this far, the ray will intersect the box
	float closestT = 1000000;
	Object* closestObj;
	Object* tmpObj;
	for (int i = 0; i < objects.size(); i++) {
		float tmpDistance;
		tmpObj = objects[i]->tIntersect(ray, tmpDistance);

		if (tmpDistance != -1 && tmpDistance < closestT) {
			closestT = tmpDistance;
			closestObj = tmpObj;
		}
	}

	if (closestT != 1000000) {
		distance = closestT;
		return closestObj;
	}
	else {
		distance = -1;
		return this;
	}
}

Vector3 BoundingBox::normal(Vector3 at) {
	return n;
}

bool BoundingBox::addObject(Object* obj) {
	//if doesn't fit, return false
	if (!contains(obj)) return false;

	//else
	for (int i = 0; i < objects.size(); i++) {
		if (!objects[i]->isBoundingVolume()) continue;

		BoundingBox* bb = (BoundingBox*)objects[i];
		if (bb->addObject(obj)) {
			objects[i] = bb;
			return true;
		}
	}

	objects.push_back(obj);
	return true;
}

void BoundingBox::split(int numTimes) {
	numTimes--;
	if (numTimes < 0) return;

	Vector3 min = bounds[0];
	Vector3 max = bounds[1];

	Vector3 center(min*0.5 + max*0.5);

	addObject(new BoundingBox(min, Vector3(center.x, max.y, center.z)));
	addObject(new BoundingBox(Vector3(center.x, min.y, center.z), max));

	Vector3 min1(center.x, min.y, min.z);
	Vector3 max1(max.x, max.y, center.z);
	addObject(new BoundingBox(min1, max1));

	Vector3 min2(min.x, min.y, center.z);
	Vector3 max2(center.x, max.y, max.z);
	addObject(new BoundingBox(min2, max2));

	int size = objects.size();
	for (int i = 0; i < 4; i++) {
		BoundingBox* bb = (BoundingBox*)objects[size-i-1];
		bb->split(numTimes);
		objects[size-i-1] = bb;
	}
}

bool BoundingBox::contains(const Object* obj) {
	Vector3 min = bounds[0];
	Vector3 max = bounds[1];

	if (obj->name == "Triangle") {
		Triangle* tri = (Triangle*)obj;
		return (tri->p0 >= min && tri->p0 <= max && tri->p1 >= min && tri->p1 <= max && tri->p2 >= min && tri->p2 <= max);
	}
	if (obj->name == "BoundingBox") {
		BoundingBox* bb = (BoundingBox*)obj;
		return (bb->bounds[0] >= min && bb->bounds[0] <= max && bb->bounds[1] >= min && bb->bounds[1] <= max);
	}

	return false;
}